// TCP 服务器 --- 第一次熟悉 TCP 接口

// Tcp 是有连接的 , 面向字节流的

// 1. 创建套接字
// 2. bind 绑定端口号
// 3.设置套接字为 listen 监听状态 --  因为是面向连接的

//  获取连接 -- accept

#pragma once
#include "Common.hpp"
#include "Log.hpp"
#include "InteInfo.hpp"

#include "ThreadPool.hpp"

#include <functional>
#include <sys/types.h>
#include <sys/wait.h>
#include <pthread.h>

// C++ 中允许传参从右到左缺省 , 但是从左到右缺省所有参数必须都缺省

using namespace LogModel;
using namespace ThreadPoolModel;
const int default_backlog = 8;
const int default_listensockfd = -1;

using server_task = std::function<void()>;

class TcpServer
{
public:
    TcpServer(uint16_t port)
        : _port(port),
          _listen_sockfd(default_listensockfd),
          _isrunning(false)
    {
    }
    ~TcpServer() {}

    void Init()
    {
        // 1. 创建套接字 , TCP
        _listen_sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if (_listen_sockfd < 0)
        {
            LOG(LogLevel::FATAL) << "socket error";
            exit(SOCKET_ERROR);
        }
        LOG(LogLevel::INFO) << "socket success";

        // 2. 绑定 -- 服务器要显示绑定端口
        // 2.1 构建网络信息  -- 本地转网络 -- 只有端口
        InteInfo local(_port);
        // 2.2 bind -- 服务器要显示绑定
        int n = bind(_listen_sockfd, local.InteAddrToSockAddr(), local.SockAddrLen());
        if (n < 0)
        {
            LOG(LogLevel::FATAL) << "bind error";
        }
        LOG(LogLevel::INFO) << "bind success";

        // 3. listen -- TCP 面向连接 , listen 监听
        // int listen(int sockfd, int backlog);
        // 第一个参数 : 要监听谁 ?? 哪个套接字
        // 第二个参数 : 一个长度 , 不能太大 , 不能太小 , 16 ..
        // 监听 listen套接字

        int lis = listen(_listen_sockfd, default_backlog);
        if (lis < 0)
        {
            LOG(LogLevel::FATAL) << "listen error";
        }
        LOG(LogLevel::INFO) << "listen success";
    }

    // 完成服务的任务
    //  1. 读取客户端发来的信息
    //  2. 写回信息到客户端
    void Server(int sockfd, InteInfo &addr)
    {
        char buffer[1024];

        while (true)
        {
            // 1. 读信息 -- read
            // read 返回值三种情况 :
            // 1. > 0 读到
            // 2. < 0 读取失败
            // 3. =0  对端把文件关了 , 我读到了文件末尾
            ssize_t red = read(sockfd, buffer, sizeof(buffer) - 1);
            if (red > 0)
            {
                // 读取成功 , 继续完成任务 - 将读取的内容写回

                // 1. 当做字符串
                buffer[red] = 0;
                LOG(LogLevel::DEBUG) << addr.InfoAddr() << " # " << buffer;

                std::string echo_string = "server say#";
                echo_string += buffer;

                // 2. 写回 -- write
                ssize_t wr = write(sockfd, echo_string.c_str(), echo_string.size());
                if (wr > 0)
                {
                    LOG(LogLevel::DEBUG) << "成功写回 ... ";
                }
            }
            else if (red == 0)
            {
                // 读到文件结尾 , 对端退出
                LOG(LogLevel::INFO) << "对端退出了 , 我也不读了 , 我也退";
                close(sockfd);
                break;
            }
            else
            {
                // 异常
                LOG(LogLevel::ERROR) << "读取异常 .. ";
                close(sockfd);
                break;
            }
        }
    }

    // 线程的数据
    class ThreadData
    {
    public:
        ThreadData(int skfd, InteInfo &inte)
            : sockfd(skfd), Addr(inte)
        {
        }
        ~ThreadData() {}

        // 线程要处理任务 , 我要获取任务吧 , 通过文件描述符
        //                 还想知道谁给我发的
        int sockfd;
        InteInfo Addr;

        // 还要访问 TcpServer 的 Server 类成员函数
        TcpServer *tcp;
    };

    // 多线程版本
    // 线程例程处理函数 -- 这个函数最终要处理的就是任务 , 通过回调任务
    static void *Routinue(void *args)
    {
        // 但是 , 线程也不能等待 , 否则也就阻塞了 , 所以设置为分离状态
        pthread_detach(pthread_self()); // 将线程自己设置为分离状态
        // 得到线程数据对象 , 这样就能访问这个结构体里的数据了
        ThreadData *td = static_cast<ThreadData *>(args);

        // 任务回调
        td->tcp->Server(td->sockfd, td->Addr);
        delete td;
        return nullptr;
    }

    // 现在是一个 Echo Server -- test 版本 -- 熟悉接口
    // 服务器运行起来干嘛 ?? 接收消息 , 回显消息
    void Run()
    {
        _isrunning = true;

        while (_isrunning)
        {
            // 1 . 获取连接 -- accept
#if 0 
            int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
            //第一个参数 : 哪个套接字 , listen_sockfd listen套接字 , 要从这里获取
            //其余两个 : 我要知道获取谁的吧 ??  是输出型参数 , 该接口会把其带回来
            ***** 返回值 ***** 
            1. 成功 , 返回一个文件描述符 ?? 对的 
            // 这个文件描述符是具体任务需要的 -- 也就是完成任务的文件描述符
            // 相当于小二 
            那监听时也有文件描述符 , 这个干嘛 ?? 这个就只是负责监听的 , 完成任务是获取连接后的描述符
            2. 失败 , -1 + error 

            3. 没有连接 , 就会阻塞等待
#endif

            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            // 这个文件描述符才是给后面用的
            int sockfd = accept(_listen_sockfd, CONV(peer), &len);
            if (sockfd < 0)
            {
                LOG(LogLevel::WARNING) << "accept error";
                continue; // 此次获取失败 , 继续获取
            }
            // 获取谁的连接呀 ,我得知道
            InteInfo who(peer); // 其实就是远端的
            LOG(LogLevel::INFO) << "accept success , who : " << who.InfoAddr();

#if 0 

            // // 2. 完成任务 --- ***************  单进程版本 -- ForTest -- 仅仅测试接口 ********
            // Server(sockfd , who);
            // ***************************************************************************

#endif

#if 0 
            //  ************************   多进程版本   ***********************************  
            // --- 多进程 , 多线程适合处理长服务 -- 用户不退就不会退 !

            // 1. 创建子进程  --  子进程是可以看到父进程打开的文件描述符的
            // 父进程打开的是什么文件描述符 ???  sockfd 和 listensockfd

            pid_t id = fork(); // 子进程 1
            if (id < 0)
            {
                LOG(LogLevel::FATAL) << "fork error";
                exit(FORK_ERR);
            }
            else if (id == 0) // 子进程 2
            {
                // 子进程
                //  1. -- 子进程不需要看到 _listen_sockfd
                close(_listen_sockfd);
                // 2. 创建子进程3  -- 即 : 该子进程的子进程
                if (fork() > 0) // 为子进程 2  , 子进程2 立马退出 , 让父进程去回收它 , 父进程就不会阻塞等待了
                    exit(OK);

                // 3. 走到这里是 子进程 3 在完成任务 --- 即 : 孙子进程在完成任务
                //    但是 , 该进程的父进程(子进程 2 ) 已经退出了 , 所以该进程就是一个孤儿进程
                //    但是 , 孤儿进程会被系统领养 , 被 1 号进程自动领养并回收

                // 完成任务
                Server(sockfd, who);
            }
            else
            {
                // 父进程 -- 父进程只用关心监听 , 不用关心 sockfd
                // 1 . 关闭不用的文件描述符
                close(sockfd);

                // 2 . 父进程等待子进程
                waitpid(id, nullptr, 0); // 阻塞等待 , 所以让父进程直接退出,其直接就被回收了 , 就不会阻塞了
            }

              // ***************************************************************************
#endif

#if 0 

            //*****************  多线程版本 *******************
            // 多线程也适合长服务 , 因为来一个用户就创建一个线程 , 线程可以一直增加 , 各自处理任务

            // 创建线程数据对象
            ThreadData *tda = new ThreadData(sockfd, who);

            // 创建线程
            pthread_t tid;
            pthread_create(&tid, nullptr, Routinue, tda);
        }

#endif
         // *****************  线程池版本 *******************
         // 因为线程池是一次创建出一批线程 , 线程数量固定 , 所以一次只允许固定数量的用户进入
         // 所以 , 线程池一般比较适合处理 : 短服务 , 第一次退一次的这种 .... 

        //构建线程池对象 -- 线程池的任务类型必须是 t() , 返回值 void ()这样的
        // 但是我们调用的 Server 要传 sockfd 和 InteInfo 类型
        //所以 , 要进行捕捉 
        ThreadPool<server_task>::GetInstance()->Equeue([this , sockfd , &who]()
        {   
            this->Server(sockfd , who);
        });



        }
        _isrunning = false;
    }

private:
    int _listen_sockfd;
    uint16_t _port;

    bool _isrunning;


};

